
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Calendar;
import java.util.Properties;

/*
 * Dev started on 22 September 2012 at approximately 18:00.
 * Bulk of dev finished by 14:00 on the 23rd.
 */
/**
 *
 * @author Aquarat
 */
public class MeterReader implements Runnable {

    public boolean run = true;
    String cs = "jdbc:mysql://localhost:3306/Metering";
    String user = "";
    String password = "";
    Connection con = null;
    Statement st = null;
    public static String config = "/etc/MeterReader/config.ini";
    public Meter[] meters;
    public Thread GC;

    public static void main(String args[]) {
        new MeterReader(args);
    }

    public MeterReader(String args[]) {
        if (args != null && args.length > 0 && args[0].indexOf("ini") > -1) {
            config = args[0] + "";
        }
        //  Runtime.getRuntime().addShutdownHook(new Thread(this, "Shutdown Thread"));
        //  init();
        try {
            go();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void init() {
        try {
            Class.forName("com.mysql.jdbc.Driver").newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        try {
            con = DriverManager.getConnection(cs, user, password);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(0);
        }
    }

    public void go() throws Exception {
        meters = getMeters();
        Thread[] readers = new Thread[meters.length];

      //  init(); //inits database

        for (int i = 0; i < readers.length; i++) {
            readers[i] = new Thread(meters[i], "Meter " + i);
            try {
                Thread.sleep((long) (i * 9));
            } catch (Exception e) {
                e.printStackTrace();
            }
            readers[i].start();
        }

        GC = new Thread(new GC(), "Garbage Collector");
        GC.start();

    }

    @Override
    public void run() {
        run = false;
        try {
            Thread.sleep(5000);
        } catch (Exception e) {
        }
        System.exit(0);
    }
   
        public Meter[] getMeters() throws Exception {
        if (new File(config).exists()) {
            Properties prop = new Properties();
            prop.load((new FileReader(config)));

            user = prop.getProperty("dbUser");
            password = prop.getProperty("dbPass");
            cs = "jdbc:mysql:" + prop.getProperty("dbUrl");

            if (prop.getProperty("Version0").indexOf("blue@aquarat.za.net") == -1
                    || prop.getProperty("Version0").indexOf("Aaron Scheiner") == -1
                    || prop.getProperty("Version0").indexOf("MeterReader") == -1) {
                System.out.println("Invalid config file found... exiting.");
                System.exit(1);
            }
            
            init();
            
            st = con.createStatement();
            
            ResultSet rs = st.executeQuery("SELECT COUNT(*) FROM Metering.Meters;");
            rs.next();
            int m = rs.getInt(1);
            rs.close();
            
            rs = st.executeQuery("SELECT * FROM Metering.Meters;");
            
            meters = new Meter[m];
            for (int i = 0; i < m; i++) {
                rs.next();
                meters[i] = new Meter();
                meters[i].ip = rs.getString(2);
                meters[i].interval = rs.getInt(3);
                meters[i].number = rs.getString(1);
            }
            rs.close();
            return meters;

        } else {
            return new Meter[1];
        }
    }

    public class Meter implements Runnable {

        int interval = 1 * 60;
        String ip = "10.0.0.49";
        String number = "000000012727"; //12 digits
        byte[] req = new byte[]{
            0x2f, 0x3f, 0x30, 0x30, 0x30, 0x30,
            0x30, 0x30, 0x30, 0x31, 0x32, 0x37,
            0x32, 0x37, 0x21, 0x0d, 0x0a
        };
        byte[] cls = new byte[]{
            0x01, 0x42, 0x30, 0x03, 0x75
        };

        @Override
        public void run() {
            Socket socket = null;
            OutputStream out = null;
            InputStreamReader in = null;

            while (run) {

                try {
                    socket = new Socket(ip, 50000);
                    out = socket.getOutputStream();
                    in = new InputStreamReader(socket.getInputStream());
                } catch (UnknownHostException e) {
                    logException(e, number);
                } catch (IOException e) {
                    logException(e, number);

                }

                retry:
                try {
                    if (socket.isConnected()) {

                        //write our meter number into the request byte array
                        for (int i = 0; i < number.length(); i++) {
                            req[i + 2] = (byte) (number.charAt(i));
                        }
                        //nihol 084 906 7300
                        //then output the array to the meter
                        out.write(req);
                        out.flush();
                        Thread.sleep(1000);

                        byte[] resp = new byte[255];
                        int bytesRead;
                        if ((bytesRead = socket.getInputStream().read(resp)) != 255) {
                            Thread.sleep(10000);
                            break retry;
                        }

                        if (bytesRead == 255 && check(resp)) {
                            float voltage1 = 0.0f;
                            float voltage2 = 0.0f;
                            float voltage3 = 0.0f;
                            float kwhtotal = 0.0f;
                            float kwh1 = 0.0f;
                            float kwh2 = 0.0f;
                            float kwh3 = 0.0f;
                            String voltage1a = "";
                            String voltage2a = "";
                            String voltage3a = "";
                            String kwhtotala = "";
                            String kwh1a = "";
                            String kwh2a = "";
                            String kwh3a = "";
                            String kwh4a = "";
                            String revkwhtota = "";
                            String revkwh1a = "";
                            String revkwh2a = "";
                            String revkwh3a = "";
                            String revkwh4a = "";
                            String time = "";
                            String power1a = "";
                            String power2a = "";
                            String power3a = "";
                            String powerTotala = "";
                            int power1 = 0;
                            int power2 = 0;
                            int power3 = 0;
                            int powerTotal = 0;
                            String errorString = "";
                            float revkwhtot = 0.0f;
                            float revkwh1 = 0.0f;
                            float revkwh2 = 0.0f;
                            float revkwh3 = 0.0f;
                            float revkwh4 = 0.0f;
                            float kwh4 = 0.0f;

                            try {
                                for (int i = 0; i < 8; i++) {
                                    if (i == 7) {
                                        kwh1a = kwh1a + ".";
                                        kwh2a = kwh2a + ".";
                                        kwh3a = kwh3a + ".";
                                        kwh4a = kwh4a + ".";
                                        kwhtotala = kwhtotala + ".";
                                        revkwhtota = revkwhtota + ".";
                                        revkwh1a = revkwh1a + ".";
                                        revkwh2a = revkwh2a + ".";
                                        revkwh3a = revkwh3a + ".";
                                        revkwh4a = revkwh4a + ".";
                                    }
                                    kwh1a = kwh1a + (char) resp[i + 24];
                                    kwh2a = kwh2a + (char) resp[i + 32];
                                    kwh3a = kwh3a + (char) resp[i + 40];
                                    kwhtotala = kwhtotala + (char) resp[i + 16];
                                    kwh4a = kwh4a + (char) resp[i + 48];
                                    revkwhtota = revkwhtota + (char) resp[i + 56];
                                    revkwh1a = revkwh1a + (char) resp[i + 64];
                                    revkwh2a = revkwh2a + (char) resp[i + 72];
                                    revkwh3a = revkwh3a + (char) resp[i + 80];
                                    revkwh4a = revkwh4a + (char) resp[i + 88];
                                }

                                kwh1 = Float.parseFloat(kwh1a);
                                kwh2 = Float.parseFloat(kwh2a);
                                kwh3 = Float.parseFloat(kwh3a);
                                kwhtotal = Float.parseFloat(kwhtotala);
                                kwh4 = Float.parseFloat(kwh4a);
                                revkwhtot = Float.parseFloat(revkwhtota);
                                revkwh1 = Float.parseFloat(revkwh1a);
                                revkwh2 = Float.parseFloat(revkwh2a);
                                revkwh3 = Float.parseFloat(revkwh3a);
                                revkwh4 = Float.parseFloat(revkwh4a);

                                for (int i = 0; i < 4; i++) {
                                    if (i == 3) {
                                        voltage1a = voltage1a + ".";
                                        voltage2a = voltage2a + ".";
                                        voltage3a = voltage3a + ".";
                                    }
                                    voltage1a = voltage1a + (char) resp[i + 96];
                                    voltage2a = voltage2a + (char) resp[i + 100];
                                    voltage3a = voltage3a + (char) resp[i + 104];
                                }

                                voltage1 = Float.parseFloat(voltage1a);
                                voltage2 = Float.parseFloat(voltage2a);
                                voltage3 = Float.parseFloat(voltage3a);

                                for (int i = 0; i < 14; i++) {
                                    time = time + (char) resp[i + 172];
                                }
                            } catch (Exception e) {
                                logException(e, resp, number);
                            }

                            //power
                            for (int i = 0; i < 7; i++) {
                                power1a = power1a + (char) resp[i + 123];
                                power2a = power2a + (char) resp[i + 130];
                                power3a = power3a + (char) resp[i + 137];
                                powerTotala = powerTotala + (char) resp[i + 144];
                            }

                            power1 = Integer.parseInt(power1a);
                            power2 = Integer.parseInt(power2a);
                            power3 = Integer.parseInt(power3a);
                            powerTotal = Integer.parseInt(powerTotala);
                            
                            boolean valid = true;
                            //check to see if the values make sense
                            if (time.length() < 5) {
                                valid = false;
                                errorString = "Time value isn't long enough.\n";
                                //break retry;
                            }

                            if ( (powerTotal/100) != (power1 + power2 + power3)/100) {
                                valid = false;
                                errorString = "Power values don't add up.\n";
                            }

                            Calendar timex = Calendar.getInstance();
                            try {

                                timex.set(Calendar.YEAR, Integer.parseInt((Calendar.getInstance().get(Calendar.YEAR) + "").substring(0, 2) + time.substring(0, 2)));
                                timex.set(Calendar.MONTH, Integer.parseInt(time.substring(2, 4)) - 1);
                                timex.set(Calendar.DAY_OF_MONTH, Integer.parseInt(time.substring(4, 6)));
                                timex.set(Calendar.HOUR_OF_DAY, Integer.parseInt(time.substring(8, 10)));
                                timex.set(Calendar.MINUTE, Integer.parseInt(time.substring(10, 12)));
                                timex.set(Calendar.SECOND, Integer.parseInt(time.substring(12, 14)));

                                if (timex.get(Calendar.YEAR) != Calendar.getInstance().get(Calendar.YEAR)
                                        && timex.get(Calendar.MONTH) != Calendar.getInstance().get(Calendar.MONTH)
                                        && timex.get(Calendar.DAY_OF_MONTH) != Calendar.getInstance().get(Calendar.DAY_OF_MONTH)) {
                                    valid = false;
                                    errorString = "Date values don't correspond.\n";
                                }

                                if(!valid)
                                {
                                    throw new Exception (errorString);
                                }
                                
                            } catch (Exception e) {
                                valid = false;
                                logException(e, resp, number);
                            }
                            
                            String tableName = "Main";
                            if (!valid) {
                                log(errorString);
                                break retry;
                                //System.out.println("Packet marked as invalid.");
                            }

                            String query = "INSERT INTO " + tableName + "(TimeOfSample, ReportedSampleTime, Data, Voltage1, Voltage2, Voltage3, kwhTotal, kwh1, kwh2, kwh3, kwh4"
                                    + ", Power1, Power2, Power3, PowerTotal, RevkwhTotal, Revkwh1, Revkwh2, Revkwh3, Revkwh4, MeterNumber)"
                                    + " VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";

                            //st = con.createStatement();
                            PreparedStatement pstmt = con.prepareStatement(query);
                            pstmt.setTimestamp(1, new java.sql.Timestamp(Calendar.getInstance().getTimeInMillis()));
                            pstmt.setTimestamp(2, new java.sql.Timestamp(timex.getTimeInMillis()));
                            pstmt.setBytes(3, resp);
                            pstmt.setFloat(4, voltage1);
                            pstmt.setFloat(5, voltage2);
                            pstmt.setFloat(6, voltage3);
                            pstmt.setFloat(7, kwh1);
                            pstmt.setFloat(8, kwh2);
                            pstmt.setFloat(9, kwh3);
                            pstmt.setFloat(10, kwh4);
                            pstmt.setFloat(11, kwhtotal);
                            pstmt.setInt(12, power1);
                            pstmt.setInt(13, power2);
                            pstmt.setInt(14, power3);
                            pstmt.setInt(15, powerTotal);
                            pstmt.setFloat(16, revkwhtot);
                            pstmt.setFloat(17, revkwh1);
                            pstmt.setFloat(18, revkwh2);
                            pstmt.setFloat(19, revkwh3);
                            pstmt.setFloat(20, revkwh4);
                            pstmt.setString(21, number);

                            pstmt.executeUpdate();

                            if (valid) {
                                log("Meter : " + number);
                                log("Voltage 1 : " + voltage1);
                                log("Voltage 2 : " + voltage2);
                                log("Voltage 3 : " + voltage3);
                                log("kWh Total : " + kwhtotal);
                                log("kWh 1 : " + kwh1);
                                log("kWh 2 : " + kwh2);
                                log("kWh 3 : " + kwh3);
                                log("Power 1 : " + power1);
                                log("Power 2 : " + power2);
                                log("Power 3 : " + power3);
                                log("Power Total : " + powerTotal);
                            } else {
                                log("Parsing error, meter : " + number);
                                log("Reason : " + errorString);
                            }
                        } else {
                            outputHexDump(resp);
                            out.write(cls);
                            out.flush();
                            log();
                            Thread.sleep(2000);
                            break retry;
                        }

                        out.write(cls);
                        out.flush();
                    }
                } catch (Exception e) {
                    logException(e, number);
                }

                try {
                    in.close();
                    out.close();
                    socket.close();
                } catch (Exception e) {
                    logException(e, number);
                }
                try {
                    Thread.sleep((long) (interval) * 1000l);
                } catch (Exception e) {
                }
            }
        }
    }
    long logCount = 0l;

    public void log(String a) {
        System.out.println(logCount + " : " + (new java.util.Date()).toString() + " : " + a);
        logCount++;
    }

    public void log() {
        log("");
    }

    public void logException(Exception e) {
        log("Exception : " + e.getMessage());
        logError(e, new byte[0], "");
    }

    public void logException(Exception e, String mn) {
        log("Exception : " + e.getMessage());
        logError(e, new byte[0], mn);
    }

    public void logException(Exception e, byte[] a, String mn) {
        log("Exception : " + e.getMessage());
        logError(e, a, mn);
    }

    public void logError(Exception e, byte[] a, String mn) {
        String stacktrace = "";

        for (int i = 0; i < e.getStackTrace().length; i++) {
            stacktrace = stacktrace + e.getStackTrace()[i] + "\n";
        }

        try {
            String query = "INSERT INTO Exceptions (Message, StackTrace, MeterResponse, TimeOfError, MeterNumber)"
                    + " VALUES(?,?,?,?,?)";
            st = con.createStatement();
            PreparedStatement pstmt = con.prepareStatement(query);
            pstmt.setTimestamp(4, new java.sql.Timestamp(Calendar.getInstance().getTimeInMillis()));
            pstmt.setString(1, e.getMessage());
            pstmt.setString(2, stacktrace);
            pstmt.setBytes(3, a);
            pstmt.setString(5, mn);

            pstmt.executeUpdate();
        } catch (Exception ee) {
            e.printStackTrace();
            ee.printStackTrace();
        }
    }

    public class GC implements java.lang.Runnable {

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(2 * 60 * 60 * 1000);
                    init();
                    Runtime.getRuntime().gc();
                } catch (Exception e) {
                }

            }
        }
    }

    public boolean checksum(byte[] a) {
        /*       byte[] c = new byte[2];
         c[0] = a[253];
         c[1] = a[254];
        
         CRC16 crc = new CRC16();
         for(int i = 1; i < a.length-3; i++)
         {
         crc.update(a[i]);
         }
        
         log("Checksum : " + Integer.toHexString((int)(c[0])) + Integer.toHexString((int)(c[1])) + " and calculated Checksum : " + Integer.toHexString(crc.value) );
         */

        return new CRC16().checksum(a);
    }

    public boolean check(byte[] a) {
        if (a[0] == 0x02 && a[1] == 0x10 && a[2] == 0x17 && a[3] == 0x13
                && a[226] == 0x00 && a[227] == 0x00 && a[228] == 0x00
                && a[249] == 0x21 && a[250] == 0x0d && a[251] == 0x0a && a[252] == 0x03) {
            return false;
        }

        try {
            byte[] c = new byte[2];
            c[0] = a[253];
            c[1] = a[254];

            log("EKM CRC : " + Integer.toHexString(ekmCheckCrc(a)) + " Device CRC : " + Integer.toHexString((int) (c[0])) + Integer.toHexString((int) (c[1])));
        } catch (Exception e) {
            e.printStackTrace();
        }

        return true;
    }

    public void outputHexDump(byte[] resp) {
        log("Signature mismatch :\n\t------------------------------------------");
        for (int i = 0; i < resp.length; i++) {
            System.out.print(pad(Integer.toHexString((int) (resp[i])) + " "));
            if (i % 16 == 0) {
                System.out.println("\t");
            }
        }
        System.out.println();
        System.out.println("\n\t------------------------------------------");
    }

    public String pad(String a) {
        if (a.length() == 1) {
            return "0" + a;
        } else {
            return a;
        }
    }

    /**
     * ***********************************************************************
     * Compilation: javac CRC16.java Execution: java CRC16 s
     *
     * Reads in a string s as a command-line argument, and prints out its 16-bit
     * Cyclic Redundancy Check (CRC16). Uses a lookup table.
     *
     * Reference: http://www.gelato.unsw.edu.au/lxr/source/lib/crc16.c
     *
     * % java CRC16 123456789 CRC16 = bb3d
     *
     * Uses irreducible polynomial: 1 + x^2 + x^15 + x^16
     *
     *
     ************************************************************************
     */
    public class CRC16 {

        public boolean checksum(byte[] a) {

            try {
                int[] table = {
                    0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
                    0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
                    0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
                    0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
                    0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
                    0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
                    0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
                    0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
                    0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
                    0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
                    0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
                    0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
                    0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
                    0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
                    0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
                    0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
                    0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
                    0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
                    0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
                    0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
                    0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
                    0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
                    0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
                    0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
                    0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
                    0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
                    0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
                    0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
                    0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
                    0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
                    0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
                    0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
                };


                byte[] bytes = a;
                int crc = 0x0000;
//            for (byte b : bytes) {
//                crc = (crc >>> 8) ^ table[(crc ^ b) & 0xff];
//            }

                //  System.out.println("CRC16 = " + Integer.toHexString(crc));

                for (int i = 0; i < a.length; i++) {
                    String bin = Integer.toBinaryString((int) (a[i]));
                    bin = bin.substring(1); //chop off 8th bit
                    crc = (crc >>> 8) ^ table[(crc ^ Integer.parseInt(bin, 2)) & 0xff];


                }

                byte[] c = new byte[2];
                c[0] = a[253];
                c[1] = a[254];
                log("Checksum : " + Integer.toHexString((int) (c[0])) + Integer.toHexString((int) (c[1])) + " and calculated Checksum : " + Integer.toHexString(crc));
                outputHexDump(a);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return true;
        }
    }

    //attempt to translate ekm meterin's crc16 c-code   
    public int ekmCheckCrc(byte[] dat) {
        int crc = 0xffff;//was 0xffff

        for (int i = 1; i < dat.length - 3; i++) {
            crc = (crc >>> 8) ^ ekmCrcLut[(crc ^ dat[i]) & 0xff];
        }

        crc = (crc >>> 8) | (crc << 8);
        crc = crc & 0x7f7f;

        return crc;
    }
    static int[] ekmCrcLut = new int[]{
        0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241,
        0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440,
        0xcc01, 0x0cc0, 0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40,
        0x0a00, 0xcac1, 0xcb81, 0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841,
        0xd801, 0x18c0, 0x1980, 0xd941, 0x1b00, 0xdbc1, 0xda81, 0x1a40,
        0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01, 0x1dc0, 0x1c80, 0xdc41,
        0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0, 0x1680, 0xd641,
        0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081, 0x1040,
        0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
        0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441,
        0x3c00, 0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41,
        0xfa01, 0x3ac0, 0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840,
        0x2800, 0xe8c1, 0xe981, 0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41,
        0xee01, 0x2ec0, 0x2f80, 0xef41, 0x2d00, 0xedc1, 0xec81, 0x2c40,
        0xe401, 0x24c0, 0x2580, 0xe541, 0x2700, 0xe7c1, 0xe681, 0x2640,
        0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0, 0x2080, 0xe041,
        0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281, 0x6240,
        0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
        0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41,
        0xaa01, 0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840,
        0x7800, 0xb8c1, 0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41,
        0xbe01, 0x7ec0, 0x7f80, 0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40,
        0xb401, 0x74c0, 0x7580, 0xb541, 0x7700, 0xb7c1, 0xb681, 0x7640,
        0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101, 0x71c0, 0x7080, 0xb041,
        0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0, 0x5280, 0x9241,
        0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481, 0x5440,
        0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
        0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841,
        0x8801, 0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40,
        0x4e00, 0x8ec1, 0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41,
        0x4400, 0x84c1, 0x8581, 0x4540, 0x8701, 0x47c0, 0x4680, 0x8641,
        0x8201, 0x42c0, 0x4380, 0x8341, 0x4100, 0x81c1, 0x8081, 0x4040
    };
}
